﻿using System.IO;
using GameFramework.Extensions;
using GameFramework.Managers;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace GameFramework.GameObjects
{
    /// <summary>
    /// This game object represent a sprite.
    /// </summary>
    public class Texture : GameObject
    {
        /// <summary>
        /// The texture.
        /// </summary>
        private Texture2D texture2D;

        /// <summary>
        /// The texture name.
        /// </summary>
        private string textureName;

        /// <summary>
        /// The coordinate x of the position.
        /// </summary>
        private float positionX;

        /// <summary>
        /// The coordinate y of the position.
        /// </summary>
        private float positionY;

        /// <summary>
        /// The coordinate x of the origin.
        /// </summary>
        private float originX;

        /// <summary>
        /// The coordinate y of the origin.
        /// </summary>
        private float originY;

        /// <summary>
        /// The coordinate x of the scale.
        /// </summary>
        private float scaleX;

        /// <summary>
        /// The coordinate y of the scale.
        /// </summary>
        private float scaleY;

        /// <summary>
        /// The angle.
        /// </summary>
        private float angle;

        /// <summary>
        /// The source rectangle.
        /// </summary>
        private Rectangle sourceRectangle;

        /// <summary>
        /// The color.
        /// </summary>
        private Color color;

        /// <summary>
        /// The layer depth.
        /// </summary>
        private float layerDepth;

        /// <summary>
        /// Initializes a new instance of the <see cref="Texture"/> class.
        /// </summary>
        public Texture()
        {
            positionX = 0;
            positionY = 0;
            originX = 0;
            originY = 0;
            scaleX = 1;
            scaleY = 1;
            angle = 0;
            sourceRectangle = Rectangle.Empty;
            color = Color.White;
            layerDepth = 0;
            textureName = string.Empty;
        }

        /// <summary>
        /// Gets or sets the x position of the object.
        /// </summary>
        public float PositionX
        {
            get { return positionX; }
            set { positionX = value; }
        }

        /// <summary>
        /// Gets or sets the y position of the object.
        /// </summary>
        public float PositionY
        {
            get { return positionY; }
            set { positionY = value; }
        }

        /// <summary>
        /// Gets or sets the x coordinate of the object orientation.
        /// </summary>
        public float OriginX
        {
            get { return originX; }
            set { originX = value; }
        }
        
        /// <summary>
        /// Gets or sets the y position of the object.
        /// </summary>
        public float OriginY
        {
            get { return originY; }
            set { originY = value; }
        }

        /// <summary>
        /// Gets or sets the x coordinate of the scale.
        /// </summary>
        public float ScaleX
        {
            get { return scaleX; }
            set { scaleX = value; }
        }

        /// <summary>
        /// Gets or sets the y position of the scale.
        /// </summary>
        public float ScaleY
        {
            get { return scaleY; }
            set { scaleY = value; }
        }

        /// <summary>
        /// Gets or sets the angle of the game object.
        /// </summary>
        public float Angle
        {
            get { return angle; }
            set { angle = value; }
        }

        /// <summary>
        /// Gets or sets the sprite color.
        /// </summary>
        public Color Color
        {
            get { return color; }
            set { color = value; }
        }

        /// <summary>
        /// Gets or sets the layer depth (the rendering order).
        /// </summary>
        public float LayerDepth
        {
            get { return layerDepth; }
            set { layerDepth = value; }
        }

        /// <summary>
        /// Gets or sets the source rectangle.
        /// </summary>
        public Rectangle SourceRectangle
        {
            get { return sourceRectangle; }
            set { sourceRectangle = value; }
        }

        /// <summary>
        /// Gets the texture.
        /// </summary>
        public Texture2D Texture2D
        {
            get { return texture2D; }
        }

        /// <summary>
        /// Gets or sets the texture name.
        /// </summary>
        public string TextureName
        { 
            get
            {
                return textureName;
            } 

            set
            { 
                textureName = value ?? string.Empty;
                TextureManager.Textures.TryGetValue(textureName, out texture2D);
            } 
        }

        #region Calculated properties

        /// <summary>
        /// Gets or sets the position of the object
        /// </summary>
        public Vector2 Position
        {
            get
            {
                return new Vector2(PositionX, PositionY);
            }

            set 
            {
                PositionX = value.X;
                PositionY = value.Y;
            }
        }

        /// <summary>
        /// Gets or sets the position of the object
        /// </summary>
        public Vector2 Origin
        {
            get
            {
                return new Vector2(OriginX, OriginY);
            }

            set
            {
                OriginX = value.X;
                OriginY = value.Y;
            }
        }

        /// <summary>
        /// Gets or sets the scale of the object.
        /// </summary>
        public Vector2 Scale
        {
            get
            {
                return new Vector2(ScaleX, ScaleY);
            }

            set
            {
                ScaleX = value.X;
                ScaleY = value.Y;
            }
        }

        /// <summary>
        /// Gets the bounding box (area of the screen in witch the sprite is drawn).
        /// The returned value is correct only if the rotation angle is zero. 
        /// If the angle is not zero the result is correct only if you change the coordinate space rotating the world by -angle.
        /// An example can be found in the <c>IsPointOverObject</c> method.
        /// </summary>
        public virtual Rectangle BoundingBox
        {
            get
            {
                var spriteSize = SourceRectangle.IsEmpty ? texture2D.GetSize() : SourceRectangle.GetSize();

                return new Rectangle(
                    (int)(PositionX - OriginX * ScaleX),
                    (int)(PositionY - OriginY * ScaleY),
                    (int)(spriteSize.X * ScaleX),
                    (int)(spriteSize.Y * ScaleY));
            }
        }

        #endregion

        /// <summary>
        /// Set the texture origin at the center.
        /// </summary>
        public void SetOriginAtCenter()
        {
            Origin = new Vector2(Texture2D.Width / 2f, Texture2D.Height / 2f);            
        }

        /// <summary>
        /// Returns <c>true</c> if the point is over the object.
        /// </summary>
        /// <param name="point">The point.</param>
        /// <returns>True if the point is over the object.</returns>
        public override bool IsPointOverObject(Vector2 point)
        {
            var boundingBox = BoundingBox;

            if (Angle == 0)
            {
                return boundingBox.Contains(point);
            }

            //// Move coordinates in the object space (relative to the Position of the bounding box)

            boundingBox.Offset(-(int)PositionX, -(int)PositionY);
            point -= Position;

            //// Rotate the point around origin by the -Angle.
            //// In this space the bounding box coordinates are correct.

            return boundingBox.Contains(point.RotateAroundOriginByAngle(-Angle));
        }

        /// <summary>
        /// Tells the game object to serialize its state into the given stream.
        /// </summary>
        /// <param name="writer">The writer.</param>
        public override void Write(BinaryWriter writer)
        {
            base.Write(writer);
            
            writer.WriteFloat(positionX);
            writer.WriteFloat(positionY);
            writer.WriteFloat(originX);
            writer.WriteFloat(originY);
            writer.WriteFloat(scaleX);
            writer.WriteFloat(scaleY);
            writer.WriteFloat(angle);
            writer.WriteRectangle(sourceRectangle);
            writer.WriteColor(color);
            writer.WriteFloat(layerDepth);

            writer.WriteString(textureName);
        }

        /// <summary>
        /// Tells the game object to deserialize its state from the given stream.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public override void Read(BinaryReader reader)
        {
            base.Read(reader);

            positionX = reader.ReadFloat();
            positionY = reader.ReadFloat();
            originX = reader.ReadFloat();
            originY = reader.ReadFloat();
            scaleX = reader.ReadFloat();
            scaleY = reader.ReadFloat();
            angle = reader.ReadFloat();
            sourceRectangle = reader.ReadRectangle();
            color = reader.ReadColor();
            layerDepth = reader.ReadFloat();
            
            TextureName = reader.ReadString();
        }

        /// <summary>
        /// Draw the sprite on the screen.
        /// </summary>
        public override void Draw()
        {
            if (!Visible || texture2D == null)
            {
                return;
            }

            if (!SourceRectangle.IsEmpty)
            {
                SpriteBatch.Draw(texture2D, Position, sourceRectangle, Color, Angle, Origin, Scale, SpriteEffects.None, LayerDepth);
            }
            else
            {
                SpriteBatch.Draw(texture2D, Position, null, Color, Angle, Origin, Scale, SpriteEffects.None, LayerDepth);
            }
        }
    }
}